package liuyang.bigeventserver.common.exception;

import jakarta.validation.ConstraintViolationException;
import liuyang.bigeventserver.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.HandlerMethodValidationException;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.util.Map;
import java.util.stream.Collectors;

/**
 * 全局异常处理
 * 注：
 * 1. 仅包含DispatcherServlet流程控制下发生的异常。
 * 2. Spring Security的异常由Spring Security框架自行处理。
 *
 * @author liuyang
 * @scine 2021/3/31
 *        2022/5/16 增加BeanValidation的统一验证处理
 *        2024/2/18 证实BeanValidation的统一验证方案依据。看一下MethodArgumentNotValidException的继承结构。
 *                  动力节点 https://www.bilibili.com/video/BV1Km4y1k7bn/?p=157&spm_id_from=pageDriver&vd_source=8bd7b24b38e3e12c558d839b352b32f4
 *        2024/2/27 DuplicateKeyException
 *        2024/2/29 ConstraintViolationException @Patter正则表达式校验异常。
 *        JWTToken的验证相关异常捕获和处理应该放置在Spring Security的过滤器链中处理。位置在AuthenticationEntryPoint中
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionControllerAdvice {

    // 依据1： 灰_灰 BeanValidation视频 3. 异常处理、级联验证 的前半部分（00:00 ~ 30:38）讲得就是一个全局异常处理方案。
    // 一般为BeanValidation时抛出 20220516 add
    //@ExceptionHandler(org.springframework.validation.BindException.class)

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        //return style1(e);
        return style2(e);
    }

    private R style1(MethodArgumentNotValidException e) {
        //log.error(e.getMessage(), e);
        StringBuilder errorInfo = new StringBuilder();
        // 前端友好命名：
        errorInfo.append(e.getBindingResult().getTarget().getClass().getSimpleName());// ok
        // 服务端友好命名：
        //errorInfo.append(e.getBindingResult().getTarget().getClass().getCanonicalName());// ok
        errorInfo.append(" : ");
        errorInfo.append(
                e.getBindingResult()
                        //.getAllErrors() // 这样写的话 error中拿不到field
                        .getFieldErrors()
                        .stream()
                        .map(error -> error.getField() + " -> " + error.getDefaultMessage())
                        .collect(Collectors.joining(",", "", "")));
        // 另注 begin
        // .collect(Collectors.toMap(FieldError::getField, FieldError:getDefaultMessage))
        // R.error也应该增加一个含Map的方法。
        // 另注 end
        return R.error(ErrorEnum.MethodArgumentNotValid.getCode(), errorInfo.toString());
    }

    private R style2(MethodArgumentNotValidException e) {
        //log.error(e.getMessage(), e);
        Map<String, String> errorInfo = e.getBindingResult()
                .getFieldErrors()
                .stream()
                .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
        return R.error(ErrorEnum.MethodArgumentNotValid.getCode(), ErrorEnum.MethodArgumentNotValid.getMsg(), errorInfo);
    }

    // 正则表达式验证规则
    // @Pattern
    @ExceptionHandler(ConstraintViolationException.class)
    public R handlerConstraintViolationException(ConstraintViolationException e) {
        //log.error(e.getMessage(), e);// 202402291815 打印StackTrace没什么必要， 其他已知异常可以酌情关闭。
        log.error(e.getMessage());
        //return R.error(ErrorEnum.ConstraintViolation.getCode(), ErrorEnum.ConstraintViolation.getMsg());
        return R.error(ErrorEnum.ConstraintViolation.getCode(), e.getMessage());
    }

    // JSON参数如果格式异常
    // e.g. org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Illegal unquoted character ((CTRL-CHAR, code 13)): has to be escaped using backslash to be included in string value
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public R handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        log.error(e.getMessage(), e);
        return R.error(ErrorEnum.HttpMessageNotReadable.getCode(), ErrorEnum.HttpMessageNotReadable.getMsg()).put("细节：", e.getMessage());
    }

    // @URL
    // e.g. org.springframework.web.method.annotation.HandlerMethodValidationException: 400 BAD_REQUEST "Validation failure"
    @ExceptionHandler(HandlerMethodValidationException.class)
    public R handlHandlerMethodValidationException(HandlerMethodValidationException e) {
        log.error(e.getMessage(), e);
        return R.error(ErrorEnum.HandlerMethodValidationException.getCode(), ErrorEnum.HandlerMethodValidationException.getMsg());
    }

    // TODO JWT Token验证异常会随着实现类的变化而变化，如果更换JWT实现类，这里注册捕获的异常相应的异常已经也相应发生变化。
    // 在这里处理不管用！！！ 应该放置在Spring Security过滤器链中。
    @ExceptionHandler(com.auth0.jwt.exceptions.TokenExpiredException.class)
    public R handlerAuth0JwtExceptionsTokenExpiredException(com.auth0.jwt.exceptions.TokenExpiredException e) {
        log.error(e.getMessage());
        return R.error(ErrorEnum.JwtTokenExpired.getCode(), e.getMessage());
    }

    // 用户注册重复异常
    @ExceptionHandler(DuplicateKeyException.class)
    public R handleDuplicateKeyException(DuplicateKeyException e) {
        log.error(e.getMessage(), e);
        return R.error(ErrorEnum.DuplicateKey.getCode(), ErrorEnum.DuplicateKey.getMsg());
    }

    // 内容协商异常，请求了一种不支持的类型
    @ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
    public R handleHttpMediaTypeNotAcceptableException(HttpMediaTypeNotAcceptableException e) {
        log.error(e.getMessage(), e);
        // TODO 只能返回406头，但内容并没有按照程序预期运行。待解决。
        /*
        2024-02-18 04:38:15.001 [http-nio-80-exec-9] WARN  o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Failure in @ExceptionHandler liuyang.testspringbootenv.common.exception.RestControllerExceptionAdvice#handleHttpMediaTypeNotAcceptableException(HttpMediaTypeNotAcceptableException)
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
         */
        return R.error(ErrorEnum.MediaTypeNotAcceptable.getCode(), ErrorEnum.MediaTypeNotAcceptable.getMsg());
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public R handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        //log.error(e.getMessage(), e);
        return R.error(ErrorEnum.MethodNotSupported.getCode(),  ErrorEnum.MethodNotSupported.getMsg());
    }

    @ExceptionHandler(NoResourceFoundException.class)
    public R handleNoResourceFoundException(NoResourceFoundException e) {
        log.error(e.getMessage(), e);
        return R.error(ErrorEnum.NoResourceFound.getCode(), ErrorEnum.NoResourceFound.getMsg());
    }

    @ExceptionHandler(Exception.class)
    public R handleException(Exception e) {
        log.error(e.getMessage(), e);
        return R.error(ErrorEnum.ServerError.getMsg());
    }
}
